SMTP認証に対応していないクライアントからlocalhostのpostfixをリレーしてAmazon SESを利用する
初めに
現行のSMTPサーバを廃止しAmazon SESへの切り替えをご相談いただくケースにおいて現行ではSMTP認証を利用しておらずIP制限のみで対応しているというケースは一定お伺いすることのあるケースである印象です。
しかしながらAmazon SESでのメール送信にIP制限を行うことは可能ですがSMTP認証およびTLS通信が必須となっているため、どうしてもアプリ(クライアント)側の改修対応が必要で移行を断念せざるを得ないということもあります。
今回はそれの解決手段の1つの選択肢としてアプリ側からの送信先として同一サーバ上(localhost)のpostifxを指定し、postfix側で認証情報を付与しリレーすることでAmazon SESを使うという方法を紹介します。
解決できる問題・解決できない問題
今回紹介する方法を使うことでアプリ側のコード変更なしにAmazon SES等のSMTP認証必須のサーバを利用することができるのでメールサーバの選択肢も大きく広がります。
また仲介するのはlocalhost上のpostfixなので認証なしのポート25の解放というリスクも減らすことができ、冗長性に関しても良くも悪くもpostfixとアプリは同一サーバのためマシン単位の障害であれば一蓮托生です。
例えば現状で独自のメールサーバ稼働している状態でその可用性やスケーリング、維持のためのサーバコストに対して課題があるのであればこの方法はそれを解決する術となるかもしれません。
アプリが対応できた順からlocalhost上でのリレーをやめ徐々にAmazon SESに直接繋ぎに行く方法を取っても良いでしょう。
一方で各サーバのpostfixの設定は一定量必要であり完全にMTAの管理からは逃れることはできません。
(localhost内のリレーかつ最終的にユーザに送信するポイントではないので多少設定量は減るかと思いますが)
むしろAPサーバ側のpostfixを利用しない管理になっていたところを手を入れるようになるため利用・管理状況になっては煩雑になる可能性もあるでしょう。
この方法を利用する前には自分の環境で最も問題となっていることは何か、利用することで解決できることがデメリットに以上のものか(もしくは今後のためになるのか)を立ち止まって考えていただければと思います。
https://docs.aws.amazon.com/ja_jp/ses/latest/dg/security-protocols.html
STARTTLS 接続の場合、Amazon SES は TLS 1.2、TLS 1.3、および SSLv2Hello をサポートします。
...
TLS ラッパー接続の場合、Amazon SES は TLS 1.2 をサポートします。
また、Amazon SESに対しての通信はTLSv1.2もしくはTLSv1.3での通信が必要となります。
アプリ側の問題ではなくpostfixやopensslといったミドルウェア自体が対応していない場合には適用できませんのでご注意ください。
実証環境
Amazon Linux 2上で試します。
$ cat /etc/os-release NAME="Amazon Linux" VERSION="2" ID="amzn" ID_LIKE="centos rhel fedora" VERSION_ID="2" PRETTY_NAME="Amazon Linux 2" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2" HOME_URL="https://amazonlinux.com/" $ postconf | grep mail_version mail_version = 2.10.1 $ openssl version OpenSSL 1.0.2k-fips 26 Jan 2017
postfixの設定はデフォルト(のはず)です。
設定
Amazon SESには特段特別な設定はしないのでここでは省略します。
趣旨自体は異なりますがマネジメントコンソール上でのSMTP認証情報の発行は先日以下の記事の一部として記載しておりますので、こちらもご参照ください。
postfixの設定
以下の設定を/etc/postfix/main.cf
に追記します。
relayhost = [email-smtp.ap-northeast-1.amazonaws.com]:587 smtp_sasl_password_maps = hash:/etc/postfix/transport_auth smtp_sasl_auth_enable = yes smtp_sasl_security_options = noanonymous smtp_tls_security_level = verify # 今回の趣旨を守るためpostfixに接続するクライアントからの接続が間違ってもTLS有効/認証有にならないように無効化 # デフォルト値でもこの挙動であったはずではあるが念のため smtpd_tls_security_level = none smtpd_sasl_auth_enable = no
/etc/postfix/transport_auth
には以下のような設定を記載します。
username
とpassword
にはAmazon SESで作成した認証情報のユーザ名とパスワードを入れます。
[email-smtp.ap-northeast-1.amazonaws.com]:587 username:password
これをpostmap
でDB化します。変更後はpostfixに読み込ませるためにreloadが必要です。
$ postmap /etc/postfix/transport_auth $ service postfix reload
認証系は確か何かSASL周りのパッケージが必要だった記憶がありますがAmazon Linux 2の場合はデフォルトで(?)入っていました。
$ yum list cyrus-sasl* Loaded plugins: extras_suggestions, langpacks, priorities, update-motd 223 packages excluded due to repository priority protections Installed Packages cyrus-sasl-lib.aarch64 2.1.26-24.amzn2 installed cyrus-sasl-plain.aarch64 2.1.26-24.amzn2 installed
もし認証周りの挙動で怪しそうであればこの辺りを確認してみてください。
送信
telnet
コマンドで直接SMTPで対話して送信します。
単発でなければmailx
コマンド等メール送信用のコマンドを入れた方が楽です。
# example.comは実際には自分の保持するドメインを入れています。 $ telnet localhost 25 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 220 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal ESMTP Postfix HELO mail.example.com 250 ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal MAIL FROM: postfix@example.com 250 2.1.0 Ok RCPT TO: receive@example.com 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> From: postfix@example.com To: receive@example.com Subject: from ses Im from SES . 250 2.0.0 Ok: queued as CF31DCBABEF QUIT 221 2.0.0 Bye Connection closed by foreign host.
送信した際のpostfixのログは以下のとおりです。
Aug 14 03:09:41 ip-xxx-xxx-xxx-xxx postfix/smtpd[19249]: connect from localhost[127.0.0.1] Aug 14 03:09:55 ip-xxx-xxx-xxx-xxx postfix/smtpd[19249]: CF31DCBABEF: client=localhost[127.0.0.1] Aug 14 03:10:02 ip-xxx-xxx-xxx-xxx postfix/cleanup[19230]: CF31DCBABEF: message-id=<20230814030955.CF31DCBABEF@ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal> Aug 14 03:10:02 ip-xxx-xxx-xxx-xxx postfix/qmgr[3428]: CF31DCBABEF: from=<postfix@example.com>, size=445, nrcpt=1 (queue active) Aug 14 03:10:03 ip-xxx-xxx-xxx-xxx postfix/smtp[19257]: CF31DCBABEF: to=<receive@example.com>, relay=email-smtp.ap-northeast-1.amazonaws.com[54.64.1.120]:587, delay=13, delays=13/0.02/0.14/0.2, dsn=2.0.0, status=sent (250 Ok 01060189f205a36a-b6171201-92c9-462b-b019-3403a5464943-000000) Aug 14 03:10:03 ip-xxx-xxx-xxx-xxx postfix/qmgr[3428]: CF31DCBABEF: removed Aug 14 03:10:05 ip-xxx-xxx-xxx-xxx postfix/smtpd[19249]: disconnect from localhost[127.0.0.1]
受信したメールは以下のとおりです。
自宅のメールサーバが諸事情で止まっているためAmazon SES(us-east-1) + Amazon S3で受信してます。
$ cat 8tpor8aqevgfuplvt7409odt534no0ecvqd0gdg1 Return-Path: <01060189f205a36a-b6171201-92c9-462b-b019-3403a5464943-000000@ap-northeast-1.amazonses.com> Received: from e234-12.smtp-out.ap-northeast-1.amazonses.com (e234-12.smtp-out.ap-northeast-1.amazonses.com [23.251.234.12]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 8tpor8aqevgfuplvt7409odt534no0ecvqd0gdg1 for receive@example.com; Mon, 14 Aug 2023 03:10:35 +0000 (UTC) X-SES-Spam-Verdict: PASS X-SES-Virus-Verdict: PASS Received-SPF: pass (spfCheck: domain of ap-northeast-1.amazonses.com designates 23.251.234.12 as permitted sender) client-ip=23.251.234.12; envelope-from=01060189f205a36a-b6171201-92c9-462b-b019-3403a5464943-000000@ap-northeast-1.amazonses.com; helo=e234-12.smtp-out.ap-northeast-1.amazonses.com; Authentication-Results: amazonses.com; spf=pass (spfCheck: domain of ap-northeast-1.amazonses.com designates 23.251.234.12 as permitted sender) client-ip=23.251.234.12; envelope-from=01060189f205a36a-b6171201-92c9-462b-b019-3403a5464943-000000@ap-northeast-1.amazonses.com; helo=e234-12.smtp-out.ap-northeast-1.amazonses.com; dkim=pass header.i=@example.com; dkim=pass header.i=@amazonses.com; dmarc=pass header.from=example.com; X-SES-RECEIPT: AEFBQUFBQUFBQUFIVXdPa2dtMFozUnZ3S2diOG9QWkw0U0hNVXIzUjNFMnJYU0I5OXg4bmNiOW0vZ1ZjbTFBRWNvcGR2bE1KWER4N3M3YjduVzgzMDBHWnJGMXFDWm9WWUVySnJkSTBRV2M2RUlaWnlia1hYOWllZENudGVjK25EQVMxTmpvdDlMaDVRQlZ3TnYyUVZCMWpVcEdHL1BaRVVXVnYycmIzbC9BdS9EWEFBTGpiMTlKZmpjcWpRTUxYMFAwSEEwQlN3d2huWWtCNmZvdEE0bS8xTTZ1dlFtMXgxQ21HemVqZE1GcDV0bEFHVkk3clRua3dJREcxTHhvaGdZeVJyWEhBZHNRT1JoamxFSG12MzdQZzY5YjQyMGJMeDhqbXBLSjRaRjhIV1Z0bXZtbk9BT09rM0Z6UXVsSE4rdU1NU1g3SEJPdkxJWDcyWFR6cmh6TVM1eHIyYzM1YkdOdlVDenRHaERHbjkyY0hoUGUzRW5Hall2OGQ3NHlPTVJWZ1VzN1QvSjd3PQ== X-SES-DKIM-SIGNATURE: a=rsa-sha256; q=dns/txt; b=PVyIOOfAnvqLXKgX7bMu5TFXrxJc6tSpvT4qSkUBU951SUN/wIbRi8v1k7VuJmqbUppBNOKhWFaRu4xH7eoj7Iz8tc43swXr3aZUAPyc62Ep1tXzc1NSKWq6rcX9EJUg8YaD53I3Iwk5oxUCuqecMPDF8/WqqiVlA0jYDIifM04=; c=relaxed/simple; s=224i4yxa5dv7c2xz3womw6peuasteono; d=amazonses.com; t=1691982636; v=1; bh=o6solQa8Aa4YV3Xbb2eudWnbQ8TTQxiUB4zEXn3S7c0=; h=From:To:Cc:Bcc:Subject:Date:Message-ID:MIME-Version:Content-Type:X-SES-RECEIPT; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=eogp72tvvmnugysm4vjttzghjnmzww36; d=example.com; t=1691982603; h=From:To:Subject:Message-Id:Date; bh=o6solQa8Aa4YV3Xbb2eudWnbQ8TTQxiUB4zEXn3S7c0=; b=cLPmiYmPBxbplo1vxSOJcscvxt/m3ktc0PdAhtVgoR7sWPaw31bDC0i8WeNXcmGL MEc/agcwNyNGete54ZJICny2hgcgbo3wjSvP/kL9ufkTWkXoGh0AgcNfig39KxZP1L1 XgXEPKzPg8trXyBW4XzmrsnTlNXWf9dMNYSIcpyrSEN2NBQ8YbL6QSt37+7pRZUJG8Q AOGE4i6MMf0Dn+66ugHkawjSu8lwYKtsyPHfzUl4BjUsWITeFxw8AET+WfauPio3+qM pPxckll7vCUJvncY+h2I5m39JpXDJjlv47TdSuGbpXK5+QpICMdXo/v5eC1p+MCDSgm KiB/QLuXZA== DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=zh4gjftm6etwoq6afzugpky45synznly; d=amazonses.com; t=1691982603; h=From:To:Subject:Message-Id:Date:Feedback-ID; bh=o6solQa8Aa4YV3Xbb2eudWnbQ8TTQxiUB4zEXn3S7c0=; b=pPx24lewgxm01W6wjgzXnMHO+/wExtnBEkmZmtHzAARJ1jbPfgXmeKYCzG20XXDp GFcgWnAcy82ghaaO1+00N5H1IRu3oKYzphHWdsZDMtkaEhJLrCeeVjtnHzVoz7Ar+Yc pQA61BC1i9pT6ibgkPJZ8X48kp7hTR8301T/G3Ik= From: postfix@example.com To: receive@example.com Subject: from ses Message-ID: <01060189f205a36a-b6171201-92c9-462b-b019-3403a5464943-000000@ap-northeast-1.amazonses.com> Date: Mon, 14 Aug 2023 03:10:03 +0000 Feedback-ID: 1.ap-northeast-1.l/gZ9AdA66AoXDKqqW7JL8Sqq70qA5ce8pt+MQx0Zi4=:AmazonSES X-SES-Outgoing: 2023.08.14-23.251.234.12 Im from SES
Amazon SESの送信イベントは以下のようになります(同時追記)。
ログの設定外していたのを忘れたので送信環境は同じですが別のタイミングで送ったものになります。
Receivedヘッダよりlocalhostのpostfixを経由して、そこからAmazon SES向けに送信されていることがわかります。
{ "eventType": "Delivery", "mail": { "timestamp": "2023-08-14T08:46:58.328Z", "source": "postfix@example.com", "sourceArn": "arn:aws:ses:ap-northeast-1:xxxxxxxxxxxx:identity/example.com", "sendingAccountId": "xxxxxxxxxxxx", "messageId": "01060189f33a1918-152cfea8-fd15-49d8-a778-e6445c97986c-000000", "destination": [ "receive@example.com" ], "headersTruncated": false, "headers": [ { "name": "Received", "value": "from ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal (ec2-43-207-xxx-xxx.ap-northeast-1.compute.amazonaws.com [43.207.xxx.xxx]) by email-smtp.amazonaws.com with SMTP (SimpleEmailService-d-RJ9XEHDO0) id tSZrKRumQhOCiKN7EFdt for receive@example.com; Mon, 14 Aug 2023 08:46:58 +0000 (UTC)" }, { "name": "Received", "value": "from mail.example.com (localhost [127.0.0.1]) by ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal (Postfix) with SMTP id 8C3C8CBABF3 for <receive@example.com>; Mon, 14 Aug 2023 08:46:44 +0000 (UTC)" }, { "name": "From", "value": "postfix@example.com" }, { "name": "To", "value": "receive@example.com" }, { "name": "Subject", "value": "from ses" }, { "name": "Message-Id", "value": "<20230814084650.8C3C8CBABF3@ip-xxx-xxx-xxx-xxx.ap-northeast-1.compute.internal>" }, { "name": "Date", "value": "Mon, 14 Aug 2023 08:46:44 +0000 (UTC)" } ], "commonHeaders": { "from": [ "postfix@example.com" ], "date": "Mon, 14 Aug 2023 08:46:44 +0000 (UTC)", "to": [ "receive@example.com" ], "messageId": "01060189f33a1918-152cfea8-fd15-49d8-a778-e6445c97986c-000000", "subject": "from ses" }, "tags": { "ses:operation": [ "SendSmtpEmail" ], "ses:configuration-set": [ "examplecom-configuration" ], "ses:source-ip": [ "43.207.xxx.xxx" ], "ses:from-domain": [ "example.com" ], "ses:caller-identity": [ "ses-smtp-user.20230808-xxxxx" ], "ses:outgoing-ip": [ "23.251.234.12" ] } }, "delivery": { "timestamp": "2023-08-14T08:47:31.069Z", "processingTimeMillis": 32741, "recipients": [ "receive@example.com" ], "smtpResponse": "250 OK cj0lrkkn6rgabtc1jrhn7e05idpcfeqaplkvnh81", "reportingMTA": "e234-12.smtp-out.ap-northeast-1.amazonses.com" } }
なお送信時にpostfix側でno mechanism available
というエラーが出た場合はsmtp_sasl_security_options
の設定の追記抜けがないかどうかを確認してください。
未設定(デフォルト)ではnoplaintext
が含まれますがAmazon SESでは平文の認証のみが有効化されているため利用できる認証方式がなく認証に失敗します。
Aug 11 13:42:08 ip-xxx-xxx-xxx-xxx postfix/smtp[2966]: 8D94ECBABE8: SASL authentication failed; cannot authenticate to server email-smtp.ap-northeast-1.amazonaws.com[3.115.249.23]: no mechanism available
念の為誤解のないように書いておくとSMTPのプロトコルとしての話で通信経路自体はTLSで暗号化されています。
※ Amazon SESの送信エンドポイントに直接繋ぎEHLOを送った結果より(以前横道にそれて確認しておいて良かったです)
EHLO mail.example.com 250-email-smtp.amazonaws.com 250-8BITMIME 250-STARTTLS 250-AUTH PLAIN LOGIN 250 Ok
終わりに
アプリ事情でSMTP認証ができず選択肢が限定ケースにおいて同サーバ内のpostfixを利用してAmazon SESを利用してみました。
諸々事情で多少クライアントが乗っているサーバの設定を増やしても最終的に顧客にメールを配信しに行くメールサービスをマネージドなものにして管理を軽減したい場合は有効となるかもしれません。
今回はAmazon SESを例として試していますがAmazon SESだからこそできることではなく別のSMTPサービスでも利用可能ですので既に一部アプリが別のメールサービスを利用していてそこに統合したい...という場合に検討してみても良いでしょう(サービス仕様や制限による点はあるため注意)。